package cn.com.anysdk.oss.exception;

import cn.com.anysdk.oss.monitor.OssMonitorManager;
import cn.com.anysdk.oss.monitor.OssOperationContext;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * OSS 异常处理器
 * 用于统一处理 OSS 操作异常，避免侵入业务代码
 */
@Slf4j
public class OssExceptionHandler {
    private static final OssExceptionHandler INSTANCE = new OssExceptionHandler();
    private final OssMonitorManager monitorManager = OssMonitorManager.getInstance();

    private OssExceptionHandler() {}

    public static OssExceptionHandler getInstance() {
        return INSTANCE;
    }

    /**
     * 处理方法调用
     * @param target 目标对象
     * @param method 方法
     * @param args 参数
     * @return 方法返回值
     */
    public Object handleMethodCall(Object target, Method method, Object[] args) throws Throwable {
        LocalDateTime startTime = LocalDateTime.now();

        try {
            // 执行原方法
            return method.invoke(target, args);
        } catch (Throwable e) {
            // 提取真实异常
            Throwable cause = e.getCause() != null ? e.getCause() : e;

            // 如果是 OssException，直接处理
            if (cause instanceof OssException) {
                handleOssException((OssException) cause, target, method, args, startTime);
            }
            // 如果是其他异常，包装成 OssException
            else {
                OssException ossException = new OssException("OSS operation failed", cause);
                handleOssException(ossException, target, method, args, startTime);
            }

            // 重新抛出异常
            throw cause;
        }
    }

    private void handleOssException(OssException exception, Object target, Method method, Object[] args, LocalDateTime startTime) {
        try {
            // 构建操作上下文
            OssOperationContext context = buildOperationContext(target, method, args, startTime);

            // 通知监控管理器
            monitorManager.handleException(exception, context);
        } catch (Exception e) {
            // 确保异常处理本身的异常不会影响主流程
            log.error("Failed to handle OSS exception", e);
        }
    }

    private OssOperationContext buildOperationContext(Object target, Method method, Object[] args, LocalDateTime startTime) {
        return OssOperationContext.builder()
            .operationType(getOperationType(method))
            .provider(getProvider(target))
            .path(getPath(args))
            .fileSize(getFileSize(args))
            .duration(System.currentTimeMillis() - startTime.toEpochSecond(java.time.ZoneOffset.UTC) * 1000)
            .build();
    }

    private OssOperationContext.OperationType getOperationType(Method method) {
        String methodName = method.getName().toLowerCase();
        if (methodName.contains("upload")) {
            return OssOperationContext.OperationType.UPLOAD;
        } else if (methodName.contains("download")) {
            return OssOperationContext.OperationType.DOWNLOAD;
        } else if (methodName.contains("delete")) {
            return OssOperationContext.OperationType.DELETE;
        } else if (methodName.contains("url")) {
            return OssOperationContext.OperationType.GET_URL;
        } else if (methodName.contains("exists")) {
            return OssOperationContext.OperationType.CHECK_EXIST;
        }
        return OssOperationContext.OperationType.UPLOAD;
    }

    private OssOperationContext.Provider getProvider(Object target) {
        String className = target.getClass().getSimpleName().toLowerCase();
        if (className.contains("aliyun")) {
            return OssOperationContext.Provider.ALIYUN;
        } else if (className.contains("tencent")) {
            return OssOperationContext.Provider.TENCENT;
        } else if (className.contains("qiniu")) {
            return OssOperationContext.Provider.QINIU;
        } else if (className.contains("local")) {
            return OssOperationContext.Provider.LOCAL;
        }
        return OssOperationContext.Provider.ALIYUN;
    }

    private String getPath(Object[] args) {
        if (args != null) {
            for (Object arg : args) {
                if (arg instanceof String) {
                    return (String) arg;
                }
            }
        }
        return null;
    }

    private long getFileSize(Object[] args) {
        if (args != null) {
            for (Object arg : args) {
                if (arg instanceof File) {
                    return ((File) arg).length();
                } else if (arg instanceof byte[]) {
                    return ((byte[]) arg).length;
                }
            }
        }
        return 0L;
    }
}